Skip to content

fix(non-interactive): promote CI=1 to hard signal, add --non-interactive flag#67

Merged
lukeocodes merged 1 commit intomainfrom
fix/non-interactive-flag
May 8, 2026
Merged

fix(non-interactive): promote CI=1 to hard signal, add --non-interactive flag#67
lukeocodes merged 1 commit intomainfrom
fix/non-interactive-flag

Conversation

@lukeocodes
Copy link
Copy Markdown
Member

Summary

Fixes a regression Nik hit while scripting:

```
$ dg listen $DEEPGRAM_BATCH_TEST_FILE
Optional features (press Enter to skip all):
Speaker diarization [Speaker 0] / [Speaker 1] … [y/n] (n):

$ dg listen https://dpgr.am/spacewalk.wav | cat
Optional features (press Enter to skip all):
Speaker diarization [Speaker 0] / [Speaker 1] … [y/n] (n):
```

`CI=1` alone was only +1 of 3 soft-signal points, and `| cat` was also +1, so neither tripped `is_agentic()` from a normal terminal. Auto-detection only worked when several signals stacked (e.g. real CI runners with non-TTY stdin + non-TTY stdout + `CI=true`). That's not what people intuit when they explicitly opt into scripting mode.

What changed

Trigger Before After
`--non-interactive` flag did not exist hard signal, registered on root group + every subcommand
`CI=1` / `CI=true` env soft (+1) hard signal
`--agent-friendly` flag hard signal unchanged
AI-tool env vars hard signal unchanged
Non-TTY stdin/stdout soft (+1 each) unchanged
`TERM=dumb` / unset `TERM` soft (+1) unchanged
`NO_COLOR` set soft (+1) unchanged
Soft threshold ≥3 of 5 ≥3 of 4 (CI moved out)

After this: `dg --non-interactive listen FILE`, `dg listen --non-interactive FILE`, and `CI=1 dg listen FILE` all skip prompts as expected.

Tests

24 new unit tests in `packages/deepctl-core/tests/unit/test_output.py::TestIsAgentic` covering every signal — hard, soft, threshold boundaries, and negative cases. Each fixture starts from a known-interactive baseline and flips one variable at a time.

```
24 passed in 0.14s
```

Docs

`README.md` gets a "Forcing non-interactive mode" subsection under `## CI / Automation` documenting all three explicit triggers.

…e flag

Regression: CI=1 alone was only +1 of 3 soft-signal points needed to flip
is_agentic() to True, so 'CI=1 dg listen file.wav' still prompted for
optional features. Same story for 'dg listen file.wav | cat' (+1 soft
point). The auto-detection only worked when several signals stacked,
which doesn't match what users intuit when they set CI=1 or pipe output.

Two fixes plus a test surface:

1. is_agentic() — promote 'CI in (true, 1)' from soft (+1) to hard signal.
   Add '--non-interactive' to the argv string-match block alongside
   '--agent-friendly'. Soft block still has stdin/stdout TTY, TERM,
   NO_COLOR for cumulative detection in environments that don't set
   anything explicit.

2. --non-interactive — register on the root group (src/deepctl/main.py)
   so 'dg --non-interactive ...' parses, and on every subcommand via
   plugin_manager mirroring --agent-friendly, so 'dg listen
   --non-interactive ...' parses too. expose_value=False on both since
   is_agentic() reads sys.argv directly.

3. Tests — packages/deepctl-core/tests/unit/test_output.py gets a fresh
   TestIsAgentic class with 24 cases covering every hard signal
   (--non-interactive, --agent-friendly, CI=1/true, CLAUDECODE,
   CLAUDE_CODE_ENTRYPOINT, CODEX_SANDBOX, CODEX_SANDBOX_NETWORK_DISABLED,
   OR_APP_NAME=Aider, aider in OR_SITE_URL), soft-signal threshold
   boundaries (1/2/3 points), TERM=dumb and unset TERM, and negative
   cases for non-truthy CI values (false, 0, empty, yes, TRUE).

4. README — new 'Forcing non-interactive mode' subsection under CI /
   Automation, documenting the flag at any argv position, the CI=1 env
   path, and the AI-tool env auto-detection.
@lukeocodes lukeocodes merged commit a25099d into main May 8, 2026
38 checks passed
@lukeocodes lukeocodes deleted the fix/non-interactive-flag branch May 8, 2026 19:12
@github-actions github-actions Bot mentioned this pull request May 8, 2026
lukeocodes added a commit that referenced this pull request May 8, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>0.2.22</summary>

## [0.2.22](v0.2.21...v0.2.22)
(2026-05-08)


### Bug Fixes

* **listen:** restrict feature prompt to bare-invocation guided flow
([6ea275b](6ea275b))
* **listen:** restrict feature prompt to bare-invocation guided flow
([#68](#68))
([ee92d1f](ee92d1f))
* **non-interactive:** promote CI to hard signal, add --non-interactive
flag
([8da4e21](8da4e21))
* **non-interactive:** promote CI=1 to hard signal, add
--non-interactive flag
([#67](#67))
([a25099d](a25099d))
</details>

<details><summary>deepctl-core: 0.2.11</summary>

##
[0.2.11](deepctl-core-v0.2.10...deepctl-core-v0.2.11)
(2026-05-08)


### Bug Fixes

* **non-interactive:** promote CI to hard signal, add --non-interactive
flag
([8da4e21](8da4e21))
* **non-interactive:** promote CI=1 to hard signal, add
--non-interactive flag
([#67](#67))
([a25099d](a25099d))
</details>

<details><summary>deepctl-cmd-listen: 0.0.12</summary>

##
[0.0.12](deepctl-cmd-listen-v0.0.11...deepctl-cmd-listen-v0.0.12)
(2026-05-08)


### Bug Fixes

* **listen:** restrict feature prompt to bare-invocation guided flow
([6ea275b](6ea275b))
* **listen:** restrict feature prompt to bare-invocation guided flow
([#68](#68))
([ee92d1f](ee92d1f))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
lukeocodes added a commit that referenced this pull request May 8, 2026
…71)

## Why

Today the \`dx-cli\` Sentry project has zero events. That's expected for
an errors-only telemetry contract on a CLI that's been live <24h — most
users haven't hit an unhandled exception. But it also means there's no
signal at all: we can't tell "is anyone running v0.2.22?" from the
dashboard.

Sessions fix that without violating the errors-only design.

## What changed

Two lines in
\`packages/deepctl-telemetry/src/deepctl_telemetry/client.py\`:

\`\`\`python
sentry_sdk.init(
    ...
auto_session_tracking=True, # NEW (already the SDK default; explicit
now)
    traces_sample_rate=0.0,        # unchanged
    profiles_sample_rate=0.0,      # unchanged
    ...
)

atexit.register(_flush_on_exit)    # NEW
\`\`\`

Plus a tiny module-level helper:

\`\`\`python
def _flush_on_exit() -> None:
    try:
        import sentry_sdk
        sentry_sdk.flush(timeout=2.0)
    except Exception:
        pass
\`\`\`

## What it gives us

After the next release lands on PyPI and a few users invoke \`dg\`:

- [Releases
dashboard](https://deepgram.sentry.io/releases/?project=4510993603362816)
populates with per-version session counts.
- Crash-free session % per release, the canonical CLI health metric.
- Stable signal that "the new version actually launched on someone's
machine" without per-command instrumentation.

## What it doesn't change

- \`traces_sample_rate=0\` and \`profiles_sample_rate=0\` are unchanged
— no transactions, no profiles.
- No new PII paths. \`before_send\` scrubbing applies to error
envelopes; session envelopes carry only release tag, environment, and
start/end timestamps.
- Opt-out paths (\`dg config set telemetry.enabled false\`,
\`DEEPCTL_TELEMETRY_DISABLED=1\`, \`--non-interactive\` once #67 lands)
all skip session tracking too — \`init_telemetry\` returns early before
\`atexit.register\` is reached, verified by
\`test_disabled_does_not_register_atexit\`.

## Why \`atexit\` and not \`Sentry.flush()\` inline somewhere

CLI commands have multiple exit paths — \`return BaseResult(...)\`,
raised exceptions, \`ctx.exit()\` from Click, sys.exit on signal, etc.
\`atexit\` is the only hook that fires on all of them. The 2-second
budget caps user-visible exit delay even if Sentry is unreachable. The
bare-\`except\` ensures a Sentry hang can never block process
termination.

## Test plan

5 new cases in \`TestSessionFlush\`:

- \`test_init_enables_auto_session_tracking\` — kwargs dict on
\`sentry_sdk.init\` includes \`auto_session_tracking=True\` and confirms
\`traces_sample_rate==0\`, \`profiles_sample_rate==0\` are still in
place.
- \`test_init_registers_atexit_flush\` — \`atexit.register\` called once
with the \`_flush_on_exit\` function reference.
- \`test_disabled_does_not_register_atexit\` — when \`is_enabled\`
returns False, neither \`sentry_sdk.init\` nor \`atexit.register\`
fires.
- \`test_flush_on_exit_calls_sentry_flush_with_2s_budget\` —
\`sentry_sdk.flush(timeout=2.0)\` is the call shape.
- \`test_flush_on_exit_swallows_exceptions\` — Sentry raising during
flush does not propagate.

Full suite: \`uv run pytest packages/deepctl-telemetry/tests/\` → 10
passed in 0.06s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant